package me.zhengjie.minbearwcs.serivce.mission.generate.outbound;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.minbearwcs.dao.mission.inf.WcsMissionDao;
import me.zhengjie.minbearwcs.dao.outbound.inf.OutboundDao;
import me.zhengjie.minbearwcs.dao.outbound.inf.OutboundOrderDao;
import me.zhengjie.minbearwcs.dao.outbound.inf.OutboundOrderDetailDao;
import me.zhengjie.minbearwcs.dao.outbound.inf.OutboundStockUpRecordDao;
import me.zhengjie.minbearwcs.dao.storage.inf.SmallStorageDao;
import me.zhengjie.minbearwcs.dao.storage.inf.StorageDao;
import me.zhengjie.minbearwcs.entity.DTO.outbound.wcs.MediumStockUp;
import me.zhengjie.minbearwcs.entity.PO.wcs.mission.WcsMission;
import me.zhengjie.minbearwcs.entity.PO.wcs.mission.status.OutboundStockUpMissionStatus;
import me.zhengjie.minbearwcs.entity.PO.wcs.mission.type.MissionType;
import me.zhengjie.minbearwcs.entity.PO.wcs.outbound.stockUp.*;
import me.zhengjie.minbearwcs.entity.DTO.transfer.Tuple;
import me.zhengjie.minbearwcs.entity.PO.wcs.outbound.OutboundOrderDetail;
import me.zhengjie.minbearwcs.entity.PO.wcs.outbound.common.OutboundContentPool;
import me.zhengjie.minbearwcs.entity.PO.wcs.outbound.status.OutboundOrderStatus;
import me.zhengjie.minbearwcs.entity.PO.wcs.storage.type.StorageType;
import me.zhengjie.minbearwcs.serivce.mission.generate.MissionGenerator;
import me.zhengjie.minbearwcs.serivce.mission.generate.type.MissionGenType;
import me.zhengjie.minbearwcs.serivce.mission.storageProcessor.locker.Floor5Or6.OutboundStorage5Or6FloorLocker;
import me.zhengjie.minbearwcs.utils.IdWorker;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class OutboundStockUpMissionGennerator implements MissionGenerator<String> {

    private final OutboundOrderDao outboundOrderDao;
    private final OutboundOrderDetailDao outboundOrderDetailDao;
    private final OutboundDao outboundDao;
    private final IdWorker idWorker;
    private final SmallStorageDao smallStorageDao;
    private final OutboundStorage5Or6FloorLocker outboundStorage5Or6FloorLocker;
    private final OutboundStockUpRecordDao outboundStockUpRecordDao;
    private final WcsMissionDao wcsMissionDao;
    private final StorageDao storageDao;

    @Override
    public MissionGenType type() {
        return MissionGenType.OUTBOUND_STOCK_UP;
    }

    @Override
    @Transactional
    public synchronized Tuple<String> generate(Object source) {
        /**
         *
         * 1.拿到所有的需要备货，但还没备货完成的单
         * 2.根据单号，获取未备货完成的明细,并把明细状态切换为 processing
         * 3.根据明细物料类型，先找尾数区，按照物料入库时间最早策略，找到中储位栈板，记录栈板以及备货数量，
         *       直到没法找到一块可以出的整板
         * 4.根据备货区储位查找策略，找到备货区的储位
         * 5.生成wcs任务
         * 6.把备货数更新到数据库，并循环执行其他明细
         *
         * 7.找到所有为processing的明细，并统计剩余的出库物料数量，找到库内的合适的栈板，
         *      ，根据备货区储位查找策略，找到备货区的储位
         * 8.生成 wcs任务
         *
         */

        // TODO:detail状态改一下

        // 1.拿到所有的需要备货，但还没备货完成的单
        List<String> stockUpOrderList = outboundOrderDao.selectNeedStockUpOutboundOrder(
                OutboundOrderStatus.INIT, OutboundContentPool.OUTBOUND_ORDER_NEED_STOCK_UP);


        if (stockUpOrderList.size() == 0) return Tuple.tuple("不存在备货订单",false);

        List<OutboundStockUpRecord> allRestList = new ArrayList<>();

        for (String stockUpOrderCode : stockUpOrderList) {
            // 2.根据单号，获取未备货完成的明细
            List<OutboundStockUpRecord> needStockUpList = new ArrayList<>();
            List<OutboundOrderDetail> outboundOrderDetailList = outboundDao.selectNeedStockDetail(stockUpOrderCode);
            for (OutboundOrderDetail outboundOrderDetail : outboundOrderDetailList) {
                needStockUpList.addAll(computeDetail(outboundOrderDetail));
            }

            // 3.根据找待分拣区储位
            // 4.生成wcs任务
            List<OutboundStockUpRecord> detailRestList = computeDestination(needStockUpList, stockUpOrderCode);
            allRestList.addAll(detailRestList);
        }

        // 5.处理尾数
//        String outboundOrderListStr = stockUpOrderList.stream()
//                .map(a -> "'" + a + "'")
//                .collect(Collectors.joining(","));
        List<StatMaterialAndNum> statMaterialAndNums = outboundDao.selectRestMaterialNum(stockUpOrderList);
        for (StatMaterialAndNum statMaterialAndNum : statMaterialAndNums) {
            computeRest(statMaterialAndNum, stockUpOrderList);
        }

        // 6.释放储位
        releaseRest(allRestList);


        return Tuple.tuple(null, true);
    }


    // 找板
    private List<OutboundStockUpRecord> computeDetail(OutboundOrderDetail outboundOrderDetail) {
        List<OutboundStockUpRecord> resList = new ArrayList<>();

        int needNum = outboundOrderDetail.getRequireNum() - outboundOrderDetail.getStockUpNum();

        // 1.找尾数区
        List<PalletSearchPo> palletSearchPoList = outboundDao.selectCanOutboundPallet(
                outboundOrderDetail.getMaterialCode(),
                needNum,
                StorageType.MANTISSA_AREA_5F
        );

        if (!palletSearchPoList.isEmpty()) {
            for (PalletSearchPo palletSearchPo : palletSearchPoList) {
                OutboundStockUpRecord outboundStockUpRecord = new OutboundStockUpRecord(null, "stock_up_" + idWorker.nextId(), outboundOrderDetail.getOutboundOrderCode(),
                        outboundOrderDetail.getOutboundDetailId(), palletSearchPo.getPalletCode(), palletSearchPo.getMaterialCode(),
                        palletSearchPo.getMaterialNum(), 5, palletSearchPo.getSmallStorageCode(), null, null,LocalDateTime.now());
                resList.add(outboundStockUpRecord);
                outboundStorage5Or6FloorLocker.lockStorage(
                        palletSearchPo.getSmallStorageCode(),
                        palletSearchPo.getPalletCode()
                );

                needNum = needNum - palletSearchPo.getMaterialNum();
            }
        }




        // 2.找5F/6F
        List<MediumStorageAndFloorNum> canUseMediumStorageList = outboundDao.selectCanOutboundMediumSotrage(
                outboundOrderDetail.getMaterialCode(),
                needNum,
                StorageType.STORAGE_AREA_5F,
                StorageType.STORAGE_AREA_6F)
                .stream()
                .distinct()
                .collect(Collectors.toList());

        for (MediumStorageAndFloorNum mediumCodeAndFloor : canUseMediumStorageList) {
            Tuple<MediumStockUp> mediumStockUpTuple = outboundStorage5Or6FloorLocker.searchAndLockStockUpMedium(
                    mediumCodeAndFloor.getMediumStorageCode(),
                    mediumCodeAndFloor.getFloorNum(),
                    outboundOrderDetail.getMaterialCode(),
                    needNum,
                    false
            );
            if (!mediumStockUpTuple.success) continue;
            if (mediumStockUpTuple.value.getOutboundStockUpRecordList().isEmpty()) continue;

            needNum = mediumStockUpTuple.value.getRestNum();
            for (OutboundStockUpRecord valueTemp : mediumStockUpTuple.value.getOutboundStockUpRecordList()) {
                valueTemp.setOutboundOrderCode(outboundOrderDetail.getOutboundOrderCode());
                valueTemp.setOutboundOrderDetailId(outboundOrderDetail.getOutboundDetailId());
            }

            resList.addAll(mediumStockUpTuple.value.getOutboundStockUpRecordList());

            if (outboundDao.selectCanOutboundMediumSotrage(
                    outboundOrderDetail.getMaterialCode(),
                    needNum,
                    StorageType.STORAGE_AREA_5F,
                    StorageType.STORAGE_AREA_6F).isEmpty()) break;
        }


        // 3.减StockUpNum
        int stockUpNum = outboundOrderDetail.getRequireNum() - needNum;
        outboundOrderDetailDao.updateStockUpNumById(outboundOrderDetail.getId(), stockUpNum);

        return resList;
    }


    public List<OutboundStockUpRecord> computeDestination(List<OutboundStockUpRecord> needStockUpList, String outboundOrderCode) {
        // 3.根据楼层找待分拣区储位
        // 3.1 先找这张单有没有储位
        // 3.2 找空中储位
        Map<Integer, List<OutboundStockUpRecord>> collectByFloor = needStockUpList.stream().collect(Collectors.groupingBy(OutboundStockUpRecord::getStartFloorNum));

        List<OutboundStockUpRecord> outboundStockUpRecord5FList = collectByFloor.getOrDefault(5, new ArrayList<>());
        List<OutboundStockUpRecord> outboundStockUpRecord6FList = collectByFloor.getOrDefault(6, new ArrayList<>());


        List<OutboundStockUpRecord> handle5FList = findDestinationByFloor(outboundStockUpRecord5FList, outboundOrderCode, StorageType.WAIT_SORT_AREA_5F, 5);
        List<OutboundStockUpRecord> handle6FList = findDestinationByFloor(outboundStockUpRecord6FList, outboundOrderCode, StorageType.WAIT_SORT_AREA_6F, 6);

        ArrayList<OutboundStockUpRecord> alreadyHandleList = new ArrayList<>();
        alreadyHandleList.addAll(handle5FList);
        alreadyHandleList.addAll(handle6FList);

        //4.生成wcs任务
        for (OutboundStockUpRecord outboundStockUpRecord : alreadyHandleList) {
            String missionId = "Task_Outbound_Stock_up_" + idWorker.nextId();
            WcsMission wcsMission = new WcsMission(null, missionId, outboundOrderCode, MissionType.OUTBOUND_STOCK_UP.name(),
                    OutboundStockUpMissionStatus.WAIT_FOR_DISPATCH.name(), LocalDateTime.now(), null, null,
                    1, outboundStockUpRecord.getStockUpId());
            wcsMissionDao.insert(wcsMission);
            outboundStockUpRecordDao.insert(outboundStockUpRecord);
        }

        // 3.3 收集没找到的 pallet
        needStockUpList.removeAll(alreadyHandleList);
        return needStockUpList;

    }

    private List<OutboundStockUpRecord> findDestinationByFloor(List<OutboundStockUpRecord> outboundStockUpRecordList, String outboundOrderCode, StorageType storageType, Integer floorNum) {
        List<OutboundStockUpRecord> alreadyHandle = new ArrayList<>();

        // 先找5F
        for (OutboundStockUpRecord stockUpDetail : outboundStockUpRecordList) {
            // 3.1 先找5F这张单有没有储位
            String mediumStorageHasNull = outboundDao.selectMediumStorageHasNullByOutboundOrder(
                    storageType,
                    outboundOrderCode
            );

            Tuple<OutboundStockUpRecord> mediumStorageHasNullTuple = Tuple.tuple(null, false);

            if (mediumStorageHasNull != null) {
                mediumStorageHasNullTuple = outboundStorage5Or6FloorLocker.lockDestinationNullInMedium(
                        mediumStorageHasNull,
                        floorNum,
                        stockUpDetail
                );
            }

            if (mediumStorageHasNullTuple.success) {
                alreadyHandle.add(stockUpDetail);
                continue;
            }

            // 3.2 没有，找空中储位
            String nullMedium = outboundDao.selectNullMediumStorage(storageType);

            if (nullMedium == null) continue;

            Tuple<OutboundStockUpRecord> nullMediumTuple = outboundStorage5Or6FloorLocker.lockDestinationNullInMedium(
                    nullMedium,
                    floorNum,
                    stockUpDetail
            );

            if (nullMediumTuple.success) {
                alreadyHandle.add(stockUpDetail);
            }

        }

        return alreadyHandle;
    }


    private void computeRest(StatMaterialAndNum statMaterialAndNum, List<String> outboundOrderListStr) {
        Integer needNum = statMaterialAndNum.getNeedNum();



        List<OutboundStockUpRecord> needHandleList = new ArrayList<>();

        // 1.尾数区找,物料在尾数区够不够
        MaterialAndNum materialAndNum = outboundDao.selectMaterialByStorageType(
                StorageType.MANTISSA_AREA_5F,
                statMaterialAndNum.getMaterialCode()
        );

        if (materialAndNum != null && materialAndNum.getMaterialNum() >= needNum) return;

        if (materialAndNum != null) {
            needNum = needNum - materialAndNum.getMaterialNum();
        }


        // 2.找5/6F库区
        List<MediumStorageAndFloorNum> canUseMediumStorageList = outboundDao.selectCanOutboundMediumSotrageNoNeedNum(
                statMaterialAndNum.getMaterialCode(),
                StorageType.STORAGE_AREA_5F,
                StorageType.STORAGE_AREA_6F
        );

        for (MediumStorageAndFloorNum mediumCodeAndFloor : canUseMediumStorageList) {
            Tuple<MediumStockUp> mediumStockUpTuple = outboundStorage5Or6FloorLocker.searchAndLockStockUpMedium(
                    mediumCodeAndFloor.getMediumStorageCode(),
                    mediumCodeAndFloor.getFloorNum(),
                    statMaterialAndNum.getMaterialCode(),
                    needNum,
                    true
            );
            if (!mediumStockUpTuple.success) continue;
            if (mediumStockUpTuple.value.getOutboundStockUpRecordList().isEmpty()) continue;

            needNum = mediumStockUpTuple.value.getRestNum();

            needHandleList.addAll(mediumStockUpTuple.value.getOutboundStockUpRecordList());

            if (needNum <= 0) break;

            if (outboundDao.selectCanOutboundMediumSotrageNoNeedNum(
                    statMaterialAndNum.getMaterialCode(),
                    StorageType.STORAGE_AREA_5F,
                    StorageType.STORAGE_AREA_6F).isEmpty()) break;
        }

        // 3.找尾数区空位
        List<OutboundStockUpRecord> handleList = new ArrayList<>();

        for (OutboundStockUpRecord outboundStockUpRecord : needHandleList) {
            // 先找有无空位
            String nullSmallStorage = storageDao.selectNullStorageByStoargeType(StorageType.MANTISSA_AREA_5F);
            if (nullSmallStorage == null) break;

            outboundStorage5Or6FloorLocker.lockDestinationStorage(nullSmallStorage, outboundStockUpRecord.getPalletCode());
            outboundStockUpRecord.setDestinationSmallStorage(nullSmallStorage);
            outboundStockUpRecord.setDestinationFloorNum(5);

            //生成wcs任务
            String missionId = "Task_Outbound_Stock_up_" + idWorker.nextId();
            WcsMission wcsMission = new WcsMission(null, missionId, outboundStockUpRecord.getOutboundOrderCode(), MissionType.OUTBOUND_STOCK_UP.name(),
                    OutboundStockUpMissionStatus.WAIT_FOR_DISPATCH.name(), LocalDateTime.now(), null, null,
                    1, outboundStockUpRecord.getStockUpId());
            wcsMissionDao.insert(wcsMission);
            outboundStockUpRecordDao.insert(outboundStockUpRecord);

            // 处理过的加进去
            handleList.add(outboundStockUpRecord);

        }


        // 4.释放储位
        needHandleList.removeAll(handleList);
        for (OutboundStockUpRecord releaseStockUp : needHandleList) {
            smallStorageDao.updateOutboundFreezeBySmallStorageCode(
                    releaseStockUp.getStartSmallStorage(),
                    null
            );
        }

        int notHandleSum = needHandleList.stream()
                .mapToInt(OutboundStockUpRecord::getMaterialNum)
                .sum();
        needNum += notHandleSum;


        // 5.减StockUpNum
        int tempRestNum = needNum >= 0 ? needNum : 0;
        int totalStockUpNum = statMaterialAndNum.getNeedNum() - tempRestNum;

        List<OutboundOrderDetail> outboundOrderDetailList = outboundDao.selectNeedStockUpOutboundDetailBymaterial(
                outboundOrderListStr,
                statMaterialAndNum.getMaterialCode()
        );

        for (OutboundOrderDetail outboundOrderDetail : outboundOrderDetailList) {
            int detailNeed = outboundOrderDetail.getRequireNum() - outboundOrderDetail.getStockUpNum();
            if (totalStockUpNum >= detailNeed) {
                // 全部备货完
                totalStockUpNum = totalStockUpNum - detailNeed;
                outboundOrderDetailDao.updateStockUpNumById(outboundOrderDetail.getId(), outboundOrderDetail.getRequireNum());
            } else {
                // 不够备货
                outboundOrderDetailDao.updateStockUpNumById(
                        outboundOrderDetail.getId(),
                        outboundOrderDetail.getStockUpNum() + totalStockUpNum
                );
                break;
            }
        }


    }


    private void releaseRest(List<OutboundStockUpRecord> allRestList) {
        for (OutboundStockUpRecord outboundStockUpRecord : allRestList) {
            OutboundOrderDetail outboundOrderDetail = outboundOrderDetailDao.selectByDetailId(outboundStockUpRecord.getOutboundOrderDetailId());
            outboundOrderDetailDao.updateStockUpNumById(
                    outboundOrderDetail.getId(),
                    outboundOrderDetail.getStockUpNum() - outboundStockUpRecord.getMaterialNum()
            );

            smallStorageDao.updateOutboundFreezeBySmallStorageCode(
                    outboundStockUpRecord.getStartSmallStorage(),
                    null
            );
        }
    }


}
